#if !defined(ximera_common_sharedobject_hpp)
#define ximera_common_sharedobject_hpp

// -----------------------------------------------------------

#include <ximera/assert.hpp>
#include <ximera/exception.hpp>

namespace ximera
{
    template <typename T>
    class SharedObject
    {
    public:
        SharedObject() : _ptr(0) {}

        explicit SharedObject(T* ptr) 
            : _ptr(ptr)
        {
            XIMERA_CHECK(ptr, XIMERA_THROW(InvalidParamException, L"Use default constructor to initialize with null ptr"));
            ptr->AddRef();
        }

        SharedObject(SharedObject const& copy) 
            : _ptr(copy._ptr)
        {
            addref();
        }

        ~SharedObject() 
        {
            release();
        }

        SharedObject& operator=(SharedObject const& rhs)
        {
            if (&rhs == this) return *this;

            reset(rhs._ptr);
            return *this;
        }

        void reset(T* ptr, bool mustAddRef = true)
        {
            if (ptr == _ptr) return;

            release();

            _ptr = ptr;
            if (mustAddRef) addref();
        }

        T* get() const 
        {
            return _ptr;
        }

        T* operator->() const 
        {
            XIMERA_ASSERT(_ptr, XIMERA_THROW(NullPointerException, L"SharedObject contains a Null Pointer"));
            return _ptr;
        }

        T& operator*() const 
        {
            XIMERA_ASSERT(_ptr, XIMERA_THROW(NullPointerException, L"SharedObject contains a Null Pointer"));
            return *_ptr;
        }

        operator T*() const 
        {
            XIMERA_ASSERT(_ptr, XIMERA_THROW(NullPointerException, L"SharedObject contains a Null Pointer"));
            return _ptr;
        }

        operator bool () const
        {
            return _ptr != NULL;
        }

    private:
        T* _ptr;

        void addref() 
        {
            if (_ptr)
                _ptr->AddRef();
        }

        void release() 
        {
            if (_ptr)
                _ptr->Release();
        }
    };

    template <typename T, typename Y>
    bool operator==(SharedObject<T> const& lhs, SharedObject<Y> const& rhs) 
    {
        return lhs.get() == rhs.get();
    }

    template <typename T, typename Y>
    bool operator!=(SharedObject<T> const& lhs, SharedObject<Y> const& rhs) 
    {
        return !(lhs == rhs);
    }

    template <typename T>
    bool operator==(SharedObject<T> const& lhs, T* rhs) 
    {
        return lhs.get() == rhs;
    }

    template <typename T>
    bool operator==(T* lhs, SharedObject<T> const& rhs) 
    {
        return rhs == lhs;
    }
}

// -----------------------------------------------------------

#endif
